package org.jheuristics.ga.operators.selectors;

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;

import org.epanetgrid.otimizacao.log.AGLogger;
import org.jheuristics.Individual;
import org.jheuristics.ga.DefaultPopulation;
import org.jheuristics.ga.GAConfig;
import org.jheuristics.ga.GAStatus;
import org.jheuristics.ga.Population;
import org.jheuristics.ga.operators.Selector;
import org.jheuristics.ga.scalers.FitnessScaler;
import org.jheuristics.util.RandomGenerator;


/**
 * TODO
 *
 * @author Marcell Manfrin, marcell@ourgrid.org, Jan 9, 2006
 */
public abstract class AbstractRouletteSelector implements Selector {
    private FitnessScaler transformation;
	private Map spinMap;
	
	private AGLogger logger = AGLogger.getInstance();

    /**
     * TODO
     *
     * @param transformation
     */
    public AbstractRouletteSelector(FitnessScaler transformation) {
        super();
		spinMap = new WeakHashMap();
        setTransformation(transformation);
    }

    /**
     * TODO
     *
     * @return Returns the transformation.
     */
    public FitnessScaler getTransformation() {
        return transformation;
    }

    /**
     * TODO
     *
     * @param transformation The transformation to set.
     */
    public void setTransformation(FitnessScaler transformation) {
        if (null == transformation) {
			// TODO: ERROR MSG
			throw new NullPointerException();
        }
        this.transformation = transformation;
    }

    /**
     * TODO
     *
     * @param population
     * @param howMany
     * @param status
     * @param config
     * @return
     * @see org.jheuristics.ga.operators.Selector#select(org.jheuristics.ga.Population, int, org.jheuristics.ga.GAStatus, org.jheuristics.ga.GAConfig)
     */
    public Individual[] select(Population population, int howMany, GAStatus status, GAConfig config) {
    	logger.debug("******** Iniciando operador de seleção*****************\n");
        if (null == population) {
			// TODO: ERROR MSG
			throw new NullPointerException();
        }
        if (0 > howMany) {
			// TODO: ERROR MSG
			throw new IllegalArgumentException();
        }
        if (null == config) {
			// TODO: ERROR MSG
			throw new NullPointerException();
        }
        if (0 == population.size() && 0 <= howMany) {
			// TODO: ERROR MSG
			throw new IllegalArgumentException();
        }
        
//        List<Individual> popOrdenada = new ArrayList<Individual>();
//        Collections.addAll(popOrdenada, population.toIndividualArray());
//        Collections.sort(popOrdenada, new Comparator<Individual>() {
//			public int compare(Individual o1, Individual o2) {
//				return o1.getFitness().compareTo(o2.getFitness());
//			}
//        });
        Individual[] popOrdenada = population.toIndividualArray();
//      Collections.addAll(popOrdenada, population.toIndividualArray());
//      Collections.sort(popOrdenada, new Comparator<Individual>() {
//			public int compare(Individual o1, Individual o2) {
//				return o1.getFitness().compareTo(o2.getFitness());
//			}
//      });
        Arrays.sort(popOrdenada, new Comparator<Individual>() {
        	public int compare(Individual o1, Individual o2) {
        		return o1.getFitness().compareTo(o2.getFitness());
        	}
        });
        
        StringBuffer buffer = new StringBuffer("******************************************************************\n");
    	buffer.append("Iniciando Seleção por SUS\n");
    	
    	buffer.append("\nCromossmomos enviados para a seleção \n");
    	for (Individual individuo : popOrdenada) {
    		buffer.append(individuo);
    		buffer.append("\n");
		}
    	
    	logger.debug(buffer);
    	buffer = new StringBuffer();
    	
		Roulette roulette = getRoulette(popOrdenada, config);
		Population result = new DefaultPopulation();
		int[] selected = roulette.spin(howMany);
		for (int i = 0; i < selected.length; i++) {
		    Individual individual = popOrdenada[selected[i]];
            result.addIndividual(individual);
        }
		buffer.append("Cromossomos selecionados:\n");
		for (Individual individual : result.toIndividualArray()) {
			buffer.append(individual);
			buffer.append("\n");
		}
		buffer.append("\n********************************************************************\n");
		logger.debug(buffer);
		logger.debug("******** Iniciando operadores de reprodução*****************");
        return result.toIndividualArray();
    }

	/**
	 * TODO
	 *
	 * @param individuals
	 * @param config
	 * @return
	 */
	protected Roulette getRoulette(Individual[] individuals, GAConfig config) {
		List individualsList = Arrays.asList(individuals);
		Roulette roulette = null;
		if (!spinMap.containsKey(individualsList) || (roulette = (Roulette) spinMap.get(individualsList)) == null) {
			Comparable[] fitness = new Comparable[individuals.length];
			for (int i = 0; i < individuals.length; i++) {
				fitness[i] = individuals[i].getFitness();
			}
			
			double[] newFitness = transformation.scale(fitness);
			
			StringBuffer buffer = new StringBuffer("******************************************************************\n");
	    	buffer.append("Iniciando Seleção por SUS\n");
	    	
	    	buffer.append("\nCromossomos enviados para a seleção [Prob. Seleção]\n");
	    	for (int i = 0; i < individuals.length; i++) {
	    		buffer.append(i+1);
	    		buffer.append(". ");
	    		buffer.append(individuals[i]);
	    		buffer.append(" ");
	    		buffer.append(newFitness[i]);
	    		buffer.append("\n");
			}
	    	logger.debug(buffer);
			roulette = createRoulette(newFitness, config.getRandomGenerator());
			spinMap.clear();
			spinMap.put(individualsList, roulette);
			
			return roulette;
		} else {
			return roulette;
		}
	}

	/**
	 * TODO
	 *
	 * @param fitness
	 * @param random
	 * @return
	 */
	protected abstract Roulette createRoulette(double[] fitness, RandomGenerator random);

}
